﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Text;
using ZOIL.DataModel;
using ZOIL.Framework.Core;
using System.Windows;
using System.ComponentModel;

namespace ZOIL.Library.Behaviors
{
    public class ShapeToShapeConnection : BasePropertyChange
    {
        #region ctor

        public ShapeToShapeConnection(IConnectionShape element1, IConnectionShape element2, ILink link)
        {
            ConnectedElement1 = element1;
            ConnectedElement2 = element2;
            Link = link;
            UpdateConnectionPoints();
        }

        #endregion

        #region public properties

        #region ConnectedElement1

        private IConnectionShape _connectedElement1;

        /// <summary>
        /// Gets or sets the ConnectedElement1 property. This observable property 
        /// indicates
        /// </summary>
        public IConnectionShape ConnectedElement1
        {
            get { return _connectedElement1; }
            set
            {
                if (_connectedElement1 != value)
                {
                    IConnectionShape old = _connectedElement1;
                    _connectedElement1 = value;
                    OnConnectedElement1Changed(old, value);
                    RaisePropertyChanged("ConnectedElement1");
                }
            }
        }

        private IDisposable _connectedElement1Subscription;

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ConnectedElement1 property.
        /// </summary>
        protected virtual void OnConnectedElement1Changed(IConnectionShape oldConnectedElement1, IConnectionShape newConnectedElement1)
        {
            if (_connectedElement1Subscription != null)
                _connectedElement1Subscription.Dispose();

            if (newConnectedElement1 is INotifyPropertyChanged)
            {
                var propertyChanged = Observable.FromEventPattern<PropertyChangedEventArgs>(newConnectedElement1, "PropertyChanged").Where(evt => evt.EventArgs.PropertyName == "ConnectionShape");
                _connectedElement1Subscription = propertyChanged.Subscribe(evt => UpdateConnectionPoints());
            }
        }

        #endregion

        #region ConnectedElement2

        private IConnectionShape _connectedElement2;

        /// <summary>
        /// Gets or sets the ConnectedElement2 property. This observable property 
        /// indicates
        /// </summary>
        public IConnectionShape ConnectedElement2
        {
            get { return _connectedElement2; }
            set
            {
                if (_connectedElement2 != value)
                {
                    IConnectionShape old = _connectedElement2;
                    _connectedElement2 = value;
                    OnConnectedElement2Changed(old, value);
                    RaisePropertyChanged("ConnectedElement2");
                }
            }
        }

        private IDisposable _connectedElement2Subscription;

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ConnectedElement2 property.
        /// </summary>
        protected virtual void OnConnectedElement2Changed(IConnectionShape oldConnectedElement2, IConnectionShape newConnectedElement2)
        {
            if (_connectedElement2Subscription != null)
                _connectedElement2Subscription.Dispose();

            if (newConnectedElement2 is INotifyPropertyChanged)
            {
                var propertyChanged = Observable.FromEventPattern<PropertyChangedEventArgs>(newConnectedElement2, "PropertyChanged").Where(evt => evt.EventArgs.PropertyName == "ConnectionShape");
                
                _connectedElement2Subscription = propertyChanged.Subscribe(
                    (evt) =>
                    {
                        UpdateConnectionPoints();
                    });
            }
        }

        #endregion

        #region ConnectionPoint1

        private Point _connectionPoint1;

        /// <summary>
        /// Gets or sets the ConnectionPoint1 property. This observable property 
        /// indicates
        /// </summary>
        public Point ConnectionPoint1
        {
            get { return _connectionPoint1; }
            set
            {
                if (_connectionPoint1 != value)
                {
                    Point old = _connectionPoint1;
                    _connectionPoint1 = value;
                    OnConnectionPoint1Changed(old, value);
                    RaisePropertyChanged("ConnectionPoint1");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ConnectionPoint1 property.
        /// </summary>
        protected virtual void OnConnectionPoint1Changed(Point oldConnectionPoint1, Point newConnectionPoint1)
        {
        }

        #endregion

        #region ConnectionPoint2

        private Point _connectionPoint2;

        /// <summary>
        /// Gets or sets the ConnectionPoint2 property. This observable property 
        /// indicates
        /// </summary>
        public Point ConnectionPoint2
        {
            get { return _connectionPoint2; }
            set
            {
                if (_connectionPoint2 != value)
                {
                    Point old = _connectionPoint2;
                    _connectionPoint2 = value;
                    OnConnectionPoint2Changed(old, value);
                    RaisePropertyChanged("ConnectionPoint2");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the ConnectionPoint2 property.
        /// </summary>
        protected virtual void OnConnectionPoint2Changed(Point oldConnectionPoint2, Point newConnectionPoint2)
        {
        }

        #endregion

        #region Link

        private ILink _link;

        /// <summary>
        /// Gets or sets the Link property. This observable property 
        /// indicates
        /// </summary>
        public ILink Link
        {
            get { return _link; }
            set
            {
                if (_link != value)
                {
                    var old = _link;
                    _link = value;
                    OnLinkChanged(old, value);
                    RaisePropertyChanged("Link");
                }
            }
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the Link property.
        /// </summary>
        protected virtual void OnLinkChanged(ILink oldLink, ILink newLink)
        {
        }

        #endregion

        #endregion

        #region private methods

        private void UpdateConnectionPoints()
        {
            ConnectionPoint2 = ConnectedElement2.ConnectionShape.GetClosestPointOnGeometry(ConnectionPoint1);
            ConnectionPoint1 = ConnectedElement1.ConnectionShape.GetClosestPointOnGeometry(ConnectionPoint2);
        }

        #endregion
    }
}
